模块化和界面

  任何实际程序都是由一些部分组成的。例如,甚至最简单的“Hello,world!”程序也涉及到至少两个部分:用户代码要求将Hello,world!打印出来,I/O系统完成打印工作。

  考虑6.1节的桌面计算器,可以将它看做是由5个部分组成:

1)、一个分析器,完成语法的分析。
2)、一个词法处理器,由字符组合出单词。
3)、一个符号表,保存字符串和值的对偶。
4)、一个驱动程序main()。
5)、一个错误处理器。

有关情况可以用如下图形所示:

这里的箭头表示“使用”。为了简化这个图。我没有表示出每个部分都依赖于错误处理的这一事实。实际上,可以将这个计算器设想为由三个部分组成,加上驱动程序和错误处理器只是为了安全。

  当一个模块使用另一个模块时,它并不需要知道有关被用模块的所有东西。理想的情况是,一个模块的大部分细节都不为其使用者所知。为此,我们就需要将一个模块和它的界面区分开来。举例来说,分析器直接依赖于词法处理器的界面(仅此而已),并不依赖于整个词法处理器。该词法处理器不过是实现了它的界面所声言的那些服务。这些情况可以用下面图形表示:

虚线箭头表示实现。我认为这才是该程序的实际结构,而我们作为程序员的工作就是在代码里忠实地表达这些东西。如果真的做到了这些,那么结果代码就会是简单的、高效的、易理解的、可维护的,如此等等,因为它是我们的基本设计的直接反映。

  下面各节将展示怎样将桌面计算器的逻辑结构弄得更清晰些,在9.3节将显示我们可能怎样物理地将有关的程序正文组织起来,以获得这样做的一些优点。这个计算器是一个很小的程序,所以,在“现实生活中”我不会费心地去对它使用名字空间和分别编译(2.4.1节、9.1节)至少不会做到这里所做的程度。这里这样做就是为了展示用于更大型的程序有用技术,又不至于使我们淹没在代码里。在实际程序里,每个由单独名字空间表示的“模块”常常会包含成百个函数、类、模板等。

  为了阐述不同的技术和语言特征,我将分阶段开发计算器的模块化。在“现实生活中”,一个程序不大可能会经过这么多阶段而成长起来。一个有经验的程序员可能从一开始就捡起某个“大致正确”的设计。当然,随着一个程序历经多年的演化,出现剧烈的结构改变也不是很罕见的事情。

  错误处理将遍及程序的整个结构。在将一个程序分解成模块时,或者(相反地)从一些模块组合出程序时,我们都必须特别注意将由错误处理造成的模块之间的相互依赖减到最小。C++提供了异常机制,用于降低检查、报告错误和处理错误之间的联系程度。因此,在讨论了如何将模块表示为名字空间(8.2节)之后,我们将阐释怎样利用异常进一步改善模块性(8.3节)。

  现存的有关模块的概念比本章和下一章将要讨论的东西多得多。例如,我们还可能使用并发执行的和相互通信的进程来表示模块性的一些重要方面。类似地,独立地址空间的使用以及不同地址空间之间的通信也是很重要的论题,这些在这里都没有讨论。我认为有关模块的这些概念在很大程度上是互相独立、彼此正交的。有趣的是,在每种情况下,将系统划分为模块都很容易,最困难的是提供跨过模块边界的安全、方便而有效的通信。

🔚